package cn.legym.garp.gateway.traffic.dynamic.watcher;

import cn.legym.garp.gateway.traffic.dynamic.SentinelDatasourceWatcher;
import cn.legym.garp.gateway.traffic.dynamic.context.SentinelPropertyProxyFactory;
import com.alibaba.cloud.sentinel.SentinelProperties;
import com.alibaba.cloud.sentinel.datasource.RuleType;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.property.PropertyListener;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.springframework.context.ApplicationContext;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * create by pipisun on 2021/9/13
 * copyright © 2017-2021 Legym Technology Co.,Ltd. All rights reserve
 * d.
 * file description:
 * last update by {} on {}
 * update description:
 */
public class InMemoryDatasourceWatcher extends AbstractSentinelDatasourceWatcher {

    private final Map<RuleType, SentinelProperty<?>> dataMap = Maps.newConcurrentMap();

    @Override
    public void onInit(ApplicationContext context, SentinelProperties sentinelProperties) {
        Arrays.stream(RuleType.values()).forEach(type -> dataMap.put(type, getManagerPropertyByRuleType(type)));
        dataMap.forEach((k, v) -> v.addListener(new Listener(k, this)));

        // dynamic proxy
        dataMap.keySet().forEach(k -> {
            SentinelProperty<?> v = dataMap.get(k);
            SentinelProperty<?> proxy = SentinelPropertyProxyFactory.create(v);
            setManagerPropertyByRuleType(k, proxy);
            dataMap.put(k, proxy);
        });
    }

    private SentinelProperty getManagerPropertyByRuleType(RuleType ruleType) {
        Class<?> managerClazz = getManagerClazz(ruleType);
        if (managerClazz == null) {
            throw new IllegalStateException("Manager not found with ruleTYpe=" + ruleType);
        }
        try {
            return getPropertyOfManager(managerClazz);
        } catch (Exception e) {
            throw new IllegalStateException("Property not found with ruleTYpe=" + ruleType);
        }
    }

    private void setManagerPropertyByRuleType(RuleType ruleType, SentinelProperty<?> property) {
        Class<?> managerClazz = getManagerClazz(ruleType);
        if (managerClazz == null) {
            throw new IllegalStateException("Manager not found with ruleTYpe=" + ruleType);
        }
        try {
            setPropertyOfManager(managerClazz, property);
        } catch (Exception e) {
            throw new IllegalStateException("Property set failed with ruleTYpe=" + ruleType, e);
        }
    }

    private Class<?> getManagerClazz(RuleType ruleType) {
        switch (ruleType) {
            case FLOW:
                return FlowRuleManager.class;
            case GW_FLOW:
                return GatewayRuleManager.class;
            case DEGRADE:
                return DegradeRuleManager.class;
            case PARAM_FLOW:
                return ParamFlowRuleManager.class;
            case SYSTEM:
                return SystemRuleManager.class;
            case AUTHORITY:
                return AuthorityRuleManager.class;
            case GW_API_GROUP:
                return GatewayApiDefinitionManager.class;
        }
        return null;
    }

    private SentinelProperty getPropertyOfManager(Class<?> managerClazz) throws Exception {
        Field propField = managerClazz.getDeclaredField("currentProperty");
        propField.setAccessible(true);
        return (SentinelProperty) propField.get(null);
    }

    private void setPropertyOfManager(Class<?> managerClazz, SentinelProperty<?> property) throws Exception {
        Method method = managerClazz.getMethod("register2Property", SentinelProperty.class);
        method.invoke(null, property);
    }

    public void triggerDataUpdate(RuleType type, Collection<?> values) {
        SentinelProperty src = dataMap.get(type);
        if (src != null) {
            src.updateValue(values);
            getListeners().forEach(listener -> listener.onChange(type, values));
        }
    }

    @Override
    public SentinelProperty<?> getSourceByType(RuleType ruleType) {
        return dataMap.get(ruleType);
    }

    private static class Listener implements PropertyListener {

        private final RuleType ruleType;
        private final SentinelDatasourceWatcher watcher;

        public Listener(RuleType ruleType, SentinelDatasourceWatcher watcher) {
            this.ruleType = ruleType;
            this.watcher = watcher;
        }

        @Override
        public void configUpdate(Object value) {
            this.watcher.getListeners().forEach(listener -> listener.onChange(this.ruleType, value));
        }

        @Override
        public void configLoad(Object value) {
            this.watcher.getListeners().forEach(listener -> listener.onChange(this.ruleType, value));
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Listener listener = (Listener) o;
            return ruleType == listener.ruleType && Objects.equals(watcher, listener.watcher);
        }

        @Override
        public int hashCode() {
            return Objects.hash(ruleType, watcher);
        }
    }
}
